/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDefaultToolbar
 *
 * Toolbar for the editor. This modifies the state of the graph
 * or inserts new cells upon mouse clicks.
 *
 * Example:
 *
 * Create a toolbar with a button to copy the selection into the clipboard,
 * and a combo box with one action to paste the selection from the clipboard
 * into the graph.
 *
 * (code)
 * var toolbar = new mxDefaultToolbar(container, editor);
 * toolbar.addItem('Copy', null, 'copy');
 *
 * var combo = toolbar.addActionCombo('More actions...');
 * toolbar.addActionOption(combo, 'Paste', 'paste');
 * (end)
 *
 * Codec:
 *
 * This class uses the <mxDefaultToolbarCodec> to read configuration
 * data into an existing instance. See <mxDefaultToolbarCodec> for a
 * description of the configuration format.
 *
 * Constructor: mxDefaultToolbar
 *
 * Constructs a new toolbar for the given container and editor. The
 * container and editor may be null if a prototypical instance for a
 * <mxDefaultKeyHandlerCodec> is created.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 * editor - Reference to the enclosing <mxEditor>.
 */
function mxDefaultToolbar(container, editor) {
  this.editor = editor;

  if (container != null && editor != null) {
    this.init(container);
  }
}

/**
 * Variable: editor
 *
 * Reference to the enclosing <mxEditor>.
 */
mxDefaultToolbar.prototype.editor = null;

/**
 * Variable: toolbar
 *
 * Holds the internal <mxToolbar>.
 */
mxDefaultToolbar.prototype.toolbar = null;

/**
 * Variable: resetHandler
 *
 * Reference to the function used to reset the <toolbar>.
 */
mxDefaultToolbar.prototype.resetHandler = null;

/**
 * Variable: spacing
 *
 * Defines the spacing between existing and new vertices in
 * gridSize units when a new vertex is dropped on an existing
 * cell. Default is 4 (40 pixels).
 */
mxDefaultToolbar.prototype.spacing = 4;

/**
 * Variable: connectOnDrop
 *
 * Specifies if elements should be connected if new cells are dropped onto
 * connectable elements. Default is false.
 */
mxDefaultToolbar.prototype.connectOnDrop = false;

/**
 * Function: init
 *
 * Constructs the <toolbar> for the given container and installs a listener
 * that updates the <mxEditor.insertFunction> on <editor> if an item is
 * selected in the toolbar. This assumes that <editor> is not null.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 */
mxDefaultToolbar.prototype.init = function (container) {
  if (container != null) {
    this.toolbar = new mxToolbar(container);

    // Installs the insert function in the editor if an item is
    // selected in the toolbar
    this.toolbar.addListener(
      mxEvent.SELECT,
      mxUtils.bind(this, function (sender, evt) {
        var funct = evt.getProperty("function");

        if (funct != null) {
          this.editor.insertFunction = mxUtils.bind(this, function () {
            funct.apply(this, arguments);
            this.toolbar.resetMode();
          });
        } else {
          this.editor.insertFunction = null;
        }
      })
    );

    // Resets the selected tool after a doubleclick or escape keystroke
    this.resetHandler = mxUtils.bind(this, function () {
      if (this.toolbar != null) {
        this.toolbar.resetMode(true);
      }
    });

    this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
    this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
  }
};

/**
 * Function: addItem
 *
 * Adds a new item that executes the given action in <editor>. The title,
 * icon and pressedIcon are used to display the toolbar item.
 *
 * Parameters:
 *
 * title - String that represents the title (tooltip) for the item.
 * icon - URL of the icon to be used for displaying the item.
 * action - Name of the action to execute when the item is clicked.
 * pressed - Optional URL of the icon for the pressed state.
 */
mxDefaultToolbar.prototype.addItem = function (title, icon, action, pressed) {
  var clickHandler = mxUtils.bind(this, function () {
    if (action != null && action.length > 0) {
      this.editor.execute(action);
    }
  });

  return this.toolbar.addItem(title, icon, clickHandler, pressed);
};

/**
 * Function: addSeparator
 *
 * Adds a vertical separator using the optional icon.
 *
 * Parameters:
 *
 * icon - Optional URL of the icon that represents the vertical separator.
 * Default is <mxClient.imageBasePath> + '/separator.gif'.
 */
mxDefaultToolbar.prototype.addSeparator = function (icon) {
  icon = icon || mxClient.imageBasePath + "/separator.gif";
  this.toolbar.addSeparator(icon);
};

/**
 * Function: addCombo
 *
 * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
 * resulting DOM node.
 */
mxDefaultToolbar.prototype.addCombo = function () {
  return this.toolbar.addCombo();
};

/**
 * Function: addActionCombo
 *
 * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
 * the given title and return the resulting DOM node.
 *
 * Parameters:
 *
 * title - String that represents the title of the combo.
 */
mxDefaultToolbar.prototype.addActionCombo = function (title) {
  return this.toolbar.addActionCombo(title);
};

/**
 * Function: addActionOption
 *
 * Binds the given action to a option with the specified label in the
 * given combo. Combo is an object returned from an earlier call to
 * <addCombo> or <addActionCombo>.
 *
 * Parameters:
 *
 * combo - DOM node that represents the combo box.
 * title - String that represents the title of the combo.
 * action - Name of the action to execute in <editor>.
 */
mxDefaultToolbar.prototype.addActionOption = function (combo, title, action) {
  var clickHandler = mxUtils.bind(this, function () {
    this.editor.execute(action);
  });

  this.addOption(combo, title, clickHandler);
};

/**
 * Function: addOption
 *
 * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
 * the resulting DOM node that represents the option.
 *
 * Parameters:
 *
 * combo - DOM node that represents the combo box.
 * title - String that represents the title of the combo.
 * value - Object that represents the value of the option.
 */
mxDefaultToolbar.prototype.addOption = function (combo, title, value) {
  return this.toolbar.addOption(combo, title, value);
};

/**
 * Function: addMode
 *
 * Creates an item for selecting the given mode in the <editor>'s graph.
 * Supported modenames are select, connect and pan.
 *
 * Parameters:
 *
 * title - String that represents the title of the item.
 * icon - URL of the icon that represents the item.
 * mode - String that represents the mode name to be used in
 * <mxEditor.setMode>.
 * pressed - Optional URL of the icon that represents the pressed state.
 * funct - Optional JavaScript function that takes the <mxEditor> as the
 * first and only argument that is executed after the mode has been
 * selected.
 */
mxDefaultToolbar.prototype.addMode = function (
  title,
  icon,
  mode,
  pressed,
  funct
) {
  var clickHandler = mxUtils.bind(this, function () {
    this.editor.setMode(mode);

    if (funct != null) {
      funct(this.editor);
    }
  });

  return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
};

/**
 * Function: addPrototype
 *
 * Creates an item for inserting a clone of the specified prototype cell into
 * the <editor>'s graph. The ptype may either be a cell or a function that
 * returns a cell.
 *
 * Parameters:
 *
 * title - String that represents the title of the item.
 * icon - URL of the icon that represents the item.
 * ptype - Function or object that represents the prototype cell. If ptype
 * is a function then it is invoked with no arguments to create new
 * instances.
 * pressed - Optional URL of the icon that represents the pressed state.
 * insert - Optional JavaScript function that handles an insert of the new
 * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
 * event and optional <mxCell> under the mouse pointer as arguments.
 * toggle - Optional boolean that specifies if the item can be toggled.
 * Default is true.
 */
mxDefaultToolbar.prototype.addPrototype = function (
  title,
  icon,
  ptype,
  pressed,
  insert,
  toggle
) {
  // Creates a wrapper function that is in charge of constructing
  // the new cell instance to be inserted into the graph
  var factory = mxUtils.bind(this, function () {
    if (typeof ptype == "function") {
      return ptype();
    } else if (ptype != null) {
      return this.editor.graph.cloneCell(ptype);
    }

    return null;
  });

  // Defines the function for a click event on the graph
  // after this item has been selected in the toolbar
  var clickHandler = mxUtils.bind(this, function (evt, cell) {
    if (typeof insert == "function") {
      insert(this.editor, factory(), evt, cell);
    } else {
      this.drop(factory(), evt, cell);
    }

    this.toolbar.resetMode();
    mxEvent.consume(evt);
  });

  var img = this.toolbar.addMode(
    title,
    icon,
    clickHandler,
    pressed,
    null,
    toggle
  );

  // Creates a wrapper function that calls the click handler without
  // the graph argument
  var dropHandler = function (graph, evt, cell) {
    clickHandler(evt, cell);
  };

  this.installDropHandler(img, dropHandler);

  return img;
};

/**
 * Function: drop
 *
 * Handles a drop from a toolbar item to the graph. The given vertex
 * represents the new cell to be inserted. This invokes <insert> or
 * <connect> depending on the given target cell.
 *
 * Parameters:
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * target - Optional <mxCell> that represents the drop target.
 */
mxDefaultToolbar.prototype.drop = function (vertex, evt, target) {
  var graph = this.editor.graph;
  var model = graph.getModel();

  if (
    target == null ||
    model.isEdge(target) ||
    !this.connectOnDrop ||
    !graph.isCellConnectable(target)
  ) {
    while (target != null && !graph.isValidDropTarget(target, [vertex], evt)) {
      target = model.getParent(target);
    }

    this.insert(vertex, evt, target);
  } else {
    this.connect(vertex, evt, target);
  }
};

/**
 * Function: insert
 *
 * Handles a drop by inserting the given vertex into the given parent cell
 * or the default parent if no parent is specified.
 *
 * Parameters:
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * parent - Optional <mxCell> that represents the parent.
 */
mxDefaultToolbar.prototype.insert = function (vertex, evt, target) {
  var graph = this.editor.graph;

  if (graph.canImportCell(vertex)) {
    var x = mxEvent.getClientX(evt);
    var y = mxEvent.getClientY(evt);
    var pt = mxUtils.convertPoint(graph.container, x, y);

    // Splits the target edge or inserts into target group
    if (graph.isSplitEnabled() && graph.isSplitTarget(target, [vertex], evt)) {
      return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
    } else {
      return this.editor.addVertex(target, vertex, pt.x, pt.y);
    }
  }

  return null;
};

/**
 * Function: connect
 *
 * Handles a drop by connecting the given vertex to the given source cell.
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * source - Optional <mxCell> that represents the source terminal.
 */
mxDefaultToolbar.prototype.connect = function (vertex, evt, source) {
  var graph = this.editor.graph;
  var model = graph.getModel();

  if (
    source != null &&
    graph.isCellConnectable(vertex) &&
    graph.isEdgeValid(null, source, vertex)
  ) {
    var edge = null;

    model.beginUpdate();
    try {
      var geo = model.getGeometry(source);
      var g = model.getGeometry(vertex).clone();

      // Moves the vertex away from the drop target that will
      // be used as the source for the new connection
      g.x = geo.x + (geo.width - g.width) / 2;
      g.y = geo.y + (geo.height - g.height) / 2;

      var step = this.spacing * graph.gridSize;
      var dist = model.getDirectedEdgeCount(source, true) * 20;

      if (this.editor.horizontalFlow) {
        g.x += (g.width + geo.width) / 2 + step + dist;
      } else {
        g.y += (g.height + geo.height) / 2 + step + dist;
      }

      vertex.setGeometry(g);

      // Fires two add-events with the code below - should be fixed
      // to only fire one add event for both inserts
      var parent = model.getParent(source);
      graph.addCell(vertex, parent);
      graph.constrainChild(vertex);

      // Creates the edge using the editor instance and calls
      // the second function that fires an add event
      edge = this.editor.createEdge(source, vertex);

      if (model.getGeometry(edge) == null) {
        var edgeGeometry = new mxGeometry();
        edgeGeometry.relative = true;

        model.setGeometry(edge, edgeGeometry);
      }

      graph.addEdge(edge, parent, source, vertex);
    } finally {
      model.endUpdate();
    }

    graph.setSelectionCells([vertex, edge]);
    graph.scrollCellToVisible(vertex);
  }
};

/**
 * Function: installDropHandler
 *
 * Makes the given img draggable using the given function for handling a
 * drop event.
 *
 * Parameters:
 *
 * img - DOM node that represents the image.
 * dropHandler - Function that handles a drop of the image.
 */
mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler) {
  var sprite = document.createElement("img");
  sprite.setAttribute("src", img.getAttribute("src"));

  // Handles delayed loading of the images
  var loader = mxUtils.bind(this, function (evt) {
    // Preview uses the image node with double size. Later this can be
    // changed to use a separate preview and guides, but for this the
    // dropHandler must use the additional x- and y-arguments and the
    // dragsource which makeDraggable returns much be configured to
    // use guides via mxDragSource.isGuidesEnabled.
    sprite.style.width = 2 * img.offsetWidth + "px";
    sprite.style.height = 2 * img.offsetHeight + "px";

    mxUtils.makeDraggable(img, this.editor.graph, dropHandler, sprite);
    mxEvent.removeListener(sprite, "load", loader);
  });

  if (mxClient.IS_IE) {
    loader();
  } else {
    mxEvent.addListener(sprite, "load", loader);
  }
};

/**
 * Function: destroy
 *
 * Destroys the <toolbar> associated with this object and removes all
 * installed listeners. This does normally not need to be called, the
 * <toolbar> is destroyed automatically when the window unloads (in IE) by
 * <mxEditor>.
 */
mxDefaultToolbar.prototype.destroy = function () {
  if (this.resetHandler != null) {
    this.editor.graph.removeListener("dblclick", this.resetHandler);
    this.editor.removeListener("escape", this.resetHandler);
    this.resetHandler = null;
  }

  if (this.toolbar != null) {
    this.toolbar.destroy();
    this.toolbar = null;
  }
};
